package org.omixed.flex;

import java.beans.*;
import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.util.ArrayList;
import java.lang.ProcessBuilder;

import org.apache.log4j.Logger;

/**
 * @author Giles Velarde
 * Acts as a daemon that maintains an fcsh process, and keeps socket open for incoming fcsh commands. 
 */
public class ShellServer
{
	
	private static ShellServer inst;
	
	private Process p;
	PrintWriter ppw;
	
	private final int port;
	private String fcshPath;
	
	ProcessObserver processLogger;
	private CommandStore commandStore;
	
	final static private Logger logger = Logger.getLogger("ShellServer");
	
	public ShellServer( final String fcshPath , final int port ) throws IOException
	{
		this.port = port;
		this.fcshPath = fcshPath;
		commandStore = new CommandStore();
		startServer();
	}
	
	protected Serializable startServer() throws IOException
	{
		final ServerSocketChannel channel = ServerSocketChannel.open();
		channel.socket().bind( new InetSocketAddress( port ) );
		final ServerSocket serverSocket = channel.socket();
		
		ProcessBuilder pb = new ProcessBuilder( fcshPath);
		pb.redirectErrorStream( true );
		p = pb.start();
		
		InputStream pis = p.getInputStream();
		processLogger = new ProcessObserver(pis);
		Thread pReaderThread = new Thread(processLogger, "Process output");
		pReaderThread.setDaemon(true);
		pReaderThread.start();
		
		ppw = new PrintWriter(p.getOutputStream());
		
		while( true )
		{
			Socket s = serverSocket.accept();
			InputStream is = s.getInputStream();
			
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader buf = new BufferedReader(isr);
			
			String inputLine;
			String commandString = "";
			
			while ((inputLine = buf.readLine()) != null) {	
				if (inputLine.equals("END."))
					break;
				commandString += inputLine;
			}
			
			logger.debug("command:");
			logger.debug(commandString);
			
			Command storedCommand = commandStore.getCommand(commandString);
			if (storedCommand != null)
			{
				commandString = "compile " + storedCommand.id;
				logger.debug("actual command:");
				logger.debug(commandString);
			}
			
			String result = processCommand(commandString);
			
			PrintWriter spw = new PrintWriter(s.getOutputStream());
			spw.println(result);
			spw.close();
			
		}
		
	}
	
	public String processCommand(final String command) throws IOException
	{
		processLogger.initWatching("Target [some.kind.of.echo] not found");
		processLogger.initBuffering();
		
		ppw.println(command);
		ppw.flush();
		
		ppw.println("info [some.kind.of.echo]\n");
		ppw.flush();
		
		processLogger.doWait();
		
		String result = processLogger.getAndStopBuffer();
		
		System.out.println("result");
		System.out.println(result);
		
		processResult(result, command);
		
		return result;
	}
	
	private void processResult(String result, String command)
	{
		String[] results = result.split("\n");
		for (int i = 0; i< results.length; i++)
		{
			
			String line = results[i];
			
			if ((line.contains("fcsh:")) && (line.contains("as the compile target id")) )
			{
				String[] split = line.split(" ");
				
				int assignedindex = getIndex("Assigned", split);
				
				if (assignedindex != -1)
				{
					int idindex = assignedindex + 1;
					
					String id = split[idindex];
					logger.debug("storing id: '"+ id + "' for command "+command);
					commandStore.addCommand(id, command);
				}
			}
		}
	}
	
	private int getIndex(String value, String[] list)
	{
		for (int i=0; i <= list.length; i++)
		{
			if (value.equals(list[i]))
			{
				return i;
			}
		}
		return -1;
	}
	
	/*
	* A representation of a command and its id assigned by fcsh.
	*/
	class Command
	{
		public String id;
		public String commandString;
		Command(String id, String commandString)
		{
			this.id = id;
			this.commandString = commandString;
		}
	}
	
	/**
	* A store of commands assigned by fcsh.
	*/
	class CommandStore
	{
		private ArrayList<Command> commands;
		CommandStore()
		{
			commands = new ArrayList<Command>();
		}
		
		public Command getCommand(String commandString)
		{
			for( java.util.Iterator i = commands.iterator(); i.hasNext(); )
			{
				Command command = (Command) i.next();
				if (command.commandString.equals(commandString))
				{
					return command;
				}
			}
			return null;
		}
		
		public void addCommand(String id, String commandString)
		{
			Command command = new Command(id, commandString);
			commands.add(command);
		}
	}
	
	
	/**
	* Observes the fcsh process and captures its output streams. 
	* Based on the ProcessReader of fcshwrapper (http://fcshwrapper.blogspot.com/)
	*/
	class ProcessObserver implements Runnable
	{
		InputStream iStream;
		StringBuffer sBuffer;
		boolean waiting;
		boolean buffering;
		String message;
		
		ProcessObserver(InputStream iStream)
		{
			this.iStream = iStream;
		}
		public void run()
		{
			try
			{
				while (true)
				{
					BufferedReader bufferedStream = new BufferedReader(new InputStreamReader(iStream));
					String line = bufferedStream.readLine();
					if(line == null)
					{
						break;
					}
					synchronized(this)
					{
						if (waiting)
						{
							if(line.contains(message))
							{
								waiting = false;
								this.notifyAll();
								continue;
							}
						}
						if(buffering)
						{
							sBuffer.append(line + "\n");
						}
					}
				}
			} catch (Exception e)
			{
				ShellServer.logger.error(e.getMessage());
			}
		}
		private synchronized void initWatching(String message)
		{
			this.message = message;
			this.waiting = true;
		}
		private synchronized void initBuffering()
		{
			this.sBuffer = new StringBuffer();
			this.buffering = true;
		}
		private synchronized String getAndStopBuffer()
		{
			this.buffering = false;
			return this.sBuffer.toString(); 
		}
		private synchronized void doWait()
		{
			while(waiting)
			{
				try{this.wait();}catch(InterruptedException e){}
			}
		}
	}
	
	
}